类型转换与反射(Java)
类型转换&检查
类型转换
强制类型转换
强制类型转换通过
(NewType)
形式实现类型的强制转换。如下例所示,将aList
强制转换为ArrayList
类型。List aList = new ArrayList(); ArrayList anArrayList = (ArrayList) aList;
Class.cast()
这是Class提供的一个类型装的方法,通过该方法可以更灵活的实现类型转换,而不用在代码编写的时候就确定要转换的类的名称。(事实上,该方法使用情况不多,Java SE5类库中仅一处有使用)
List aList = new ArrayList(); Class<ArrayList> arrayListClass = ArrayList.class; ArrayList anArrayList = arrayListClass.cast(aList);
类型检查
- instanceof运算符,左操作数为对象,右操作数为类名
- Class.isInstance()方法,参数为对象实例
Class.isAssignableFrom()方法,参数为Class对象
SuperTest superBean = new SuperTest();
ExtendTest extendBean = new ExtendTest();Class<?> superClass = SuperTest.class;
Class<?> extendClass = ExtendTest.class;System.out.println(extendBean instanceof SuperTest); // true
System.out.println(superBean instanceof ExtendTest); // false
System.out.println(extendClass.isInstance(extendBean)); // true
System.out.println(superClass.isAssignableFrom(extendClass)); // true
System.out.println(superClass.isAssignableFrom(superClass)); // true
反射
Java的反射通过Class类和java.lang.reflect类库实现。通过它们,可以动态获得类的构造器、方法、数据成员等信息并创建新对象、执行方法和设置数据成员。
获取/修改类的方法和数据成员
通过反射可以获得类的所有构造器、方法和数据成员,即便它们是被封装在类内部(private/protected/default)。因此,使用反射需要考虑,对违反访问权限的操作是否有风险(如日后更新导致封装改变)。
方法的访问与调用
如下例所示,通过反射获得Random
的nextInt(int n)
方法并执行。
Random rand = new Random();
Method randNextIntMethod = rand.getClass().getDeclaredMethod("nextInt", int.class);
randNextIntMethod.setAccessible(true);
System.out.println(randNextIntMethod.invoke(rand, 10));
域的访问与修改
如下例所示,通过反射读取并修改Thread
的私有域threadLocals
。
Thread thread = Thread.currentThread();
Field threadLocalsField = thread.getClass().getDeclaredField("threadLocals");
threadLocalsField.setAccessible(true);
System.out.println(threadLocalsField.get(thread));
threadLocalsField.set(thread, null);
System.out.println(threadLocalsField.get(thread));
动态代理
代理是基本的设计模式之一,可以在不改变现有类的结构基础上,为接口添加额外的操作,如日志记录,度量方法调用开销等。
对于代理模式,代理的是确定的接口或类,而动态代理则在此基础上,可动态创建代理并调用代理方法,而不必局限于编译时指定的接口或类。一个动态代理的示例如下,它可以记录代理的方法调用的开销。需要注意的是,invoke
方法中,传入的proxy
为DynamicProxy
的实例本身,实际调用的是DynamicProxy
的数据成员proxied
。
/**
* 方法调用开销代理
*/
class DynamicProxy implements InvocationHandler {
private Object proxied;
public DynamicProxy(Object proxied) {
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("** proxy:" + proxied.getClass() + ", method:"
+ method.getName() + ", parameter types:"
+ Arrays.toString(method.getParameterTypes()));
long startTime = System.nanoTime();
Object result = method.invoke(proxied, args);
long endTime = System.nanoTime();
System.out.printf(" cost time: %f ms\n", (endTime - startTime)/10e6);
return result;
}
}
class DynamicProxyDemo {
public static void main(String[] args) {
List<String> aList = new ArrayList<>();
List<String> listProxy = (List<String>) Proxy.newProxyInstance(
List.class.getClassLoader(), new Class[] { List.class },
new DynamicProxy(aList));
listProxy.add("add a string");
listProxy.get(0);
}
}
/**Output:
** proxy:class java.util.ArrayList, method:add, parameter types:[class java.lang.Object]
cost time: 0.030873 ms
** proxy:class java.util.ArrayList, method:get, parameter types:[int]
cost time: 0.001478 ms
*/
空对象
使用空对象代替null,可以减少许多对null检查的代码,提高代码优雅性。当然,仍然需要对空对象的检查。可以通过创建一个空的Null
接口,将实现该接口的对象作为空对象使用。典型的,
若为一个类创建空对象,则创建一个该类的子类,子类实现
Null
接口,将子类对象作为该类的空对象将空对象作为单例使用(final static),可以通过
==
代替Null
接口检查,判断一个对象是不是空对象。若为一系列实现公共接口的类创建空对象,则通过动态代理,创建一个实现了公共接口和
Null
接口的类,将该类对象作为空对象。特别地,为了标注空对象属于哪个实现类,可通过在空对象中添加实现类的类型数据,方便辨识。